package sf.querydsl;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.dsl.ArrayPath;
import com.querydsl.core.types.dsl.BooleanPath;
import com.querydsl.core.types.dsl.ComparablePath;
import com.querydsl.core.types.dsl.DatePath;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.EnumPath;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.SimplePath;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.core.types.dsl.TimePath;
import com.querydsl.sql.ColumnMetadata;
import com.querydsl.sql.ForeignKey;
import com.querydsl.sql.PrimaryKey;
import com.querydsl.sql.RelationalPathBase;
import sf.spring.util.Assert;
import sf.spring.util.CollectionUtils;
import sf.spring.util.LinkedCaseInsensitiveMap;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.querydsl.core.types.PathMetadataFactory.forVariable;

/**
 * 动态关系表模型匹配
 * @param <A>
 */
public class DynamicSQLRelationalPath<A> extends RelationalPathBase<Object> {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    /**
     * 字段名称,对应的列
     */
    protected final Map<String, Expression<?>> map = new LinkedCaseInsensitiveMap<>();

    private static Field columnMetadataField = null;

    static {
        try {
            columnMetadataField = RelationalPathBase.class.getDeclaredField("columnMetadata");
            columnMetadataField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    public Map<String, Expression<?>> getMap() {
        return map;
    }

    private DynamicSQLRelationalPath(Class<?> type, String variable, String schema, String table) {
        super(type, variable, schema, table);
    }

    private DynamicSQLRelationalPath(Class<?> type, PathMetadata metadata, String schema, String table) {
        super(type, metadata, schema, table);
    }


    /**
     * @param variable 默认为table名称
     */
    public DynamicSQLRelationalPath(String variable) {
        this(LinkedCaseInsensitiveMap.class, forVariable(variable), "null", variable);
    }

    /**
     * @param variable 默认为table名称
     * @param schema
     * @param table
     */
    public DynamicSQLRelationalPath(String variable, String schema, String table) {
        this(LinkedCaseInsensitiveMap.class, forVariable(variable), schema, table);
    }

    /**
     * @param metadata
     * @param schema
     * @param table
     */
    public DynamicSQLRelationalPath(PathMetadata metadata, String schema, String table) {
        this(Map.class, metadata, schema, table);
    }

    /**
     * 添加动态表模型.
     * @param columnMetadata 格式为ColumnMetadata.named("available").withIndex(2).ofType(Types.BIT).withSize(1)
     * @param fieldName      字段名称
     */
    public void addColumn(ColumnMetadata columnMetadata, String fieldName) {
        Assert.notNull(columnMetadata, "column metadata is null");
        Assert.notNull(columnMetadata.getName(), "column metadata name is null");
        Assert.notNull(columnMetadata.getJdbcType(), "column metadata sql type is null");
        Expression<?> path = getBeanMappingType(fieldName);
        map.put(fieldName, path);
        //添加到列中
        addMetadata((Path<?>) path, columnMetadata);
    }

    /**
     * 删除动态表模型.
     * @param fieldNames 字段名称
     */
    public void removeColumns(String... fieldNames) {
        Assert.notNull(fieldNames, "column metadata is null");
        Assert.notEmpty(fieldNames, "column metadata is empty");
        Map<Path<?>, ColumnMetadata> pathColumnMetadataMap = Collections.emptyMap();
        try {
            pathColumnMetadataMap = (Map<Path<?>, ColumnMetadata>) columnMetadataField.get(this);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        for (Iterator<Map.Entry<Path<?>, ColumnMetadata>> it = pathColumnMetadataMap.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Path<?>, ColumnMetadata> entry = it.next();
            Path<?> path = entry.getKey();
            for (String fieldName : fieldNames) {
                if (fieldName.equalsIgnoreCase(path.getMetadata().getName())) {
                    it.remove();
                    break;
                }
            }
        }
    }

    /**
     * 添加主键,需要先添加列后才能运行.
     * @param columns
     */
    public void addPrimaryKey(String... columns) {
        Assert.notEmpty(columns, "column metadata is empty");
        int i = 0;
        Path<?>[] pkPaths = new Path[columns.length];
        Path<?>[] all = all();
        for (int j = 0; j < all.length; j++) {
            Path path = all[j];
            for (int k = 0; k < columns.length; k++) {
                String column = columns[k];
                if (Objects.equals(column, path.getMetadata().getElement())) {
                    pkPaths[i++] = (Path<?>) path;
                    break;
                }
            }
        }
        if (pkPaths.length > 0) {
            createPrimaryKey(pkPaths);
        }
    }


    /**
     * 新创建
     * @param field
     * @return
     */
    public StringPath string(String field) {
        existField(field);
        return createString(field);
    }

    /**
     * 新创建
     * @param field
     * @param tClass
     * @param <T>
     * @return
     */
    public <T extends Number & Comparable<?>> NumberPath<T> number(String field, Class<T> tClass) {
        existField(field);
        return createNumber(field, tClass);
    }

    /**
     * 新创建
     * @param field
     * @return
     */
    public BooleanPath bool(String field) {
        existField(field);
        return createBoolean(field);
    }


    public <T extends Enum<T>> EnumPath<T> enums(String field, Class<T> clz) {
        existField(field);
        return createEnum(field, clz);
    }

    public <T> SimplePath<T> simple(String field, Class<? super T> clz) {
        existField(field);
        return createSimple(field, clz);
    }

    public <A, E> ArrayPath<A, E> array(String field, Class<? super A> type) {
        existField(field);
        return createArray(field, type);
    }

    public <T extends Comparable> ComparablePath<T> comparable(String field, Class<T> clz) {
        existField(field);
        return createComparable(field, clz);
    }

    public <A extends Comparable> DatePath<A> date(String field, Class<? super A> type) {
        existField(field);
        return createDate(field, type);
    }

    public <A extends Comparable> DateTimePath<A> dateTime(String field, Class<? super A> type) {
        existField(field);
        return createDateTime(field, type);
    }

    public <A extends Comparable> TimePath<A> time(String field, Class<? super A> type) {
        existField(field);
        return createTime(field, type);
    }

    public <A> Path<A> path(String field) {
        existField(field);
        return (Path<A>) map.get(field);
    }

    public  Expression<?> expression(String field) {
        existField(field);
        return map.get(field);
    }

    protected void existField(String field) {
        Expression<?> path = map.get(field);
        if (path == null) {
            //字段不存在
            throw new RuntimeException("field not exists!");
        }
    }


    public Expression<?> getBeanMappingType(String fieldName) {
        return createString(fieldName);
    }

    protected Expression<?> copyPath(Path<?> path) {
        String fieldName = path.getMetadata().getName();
        return getBeanMappingType(fieldName);
    }

    private Class<?> getQueryType(Path<?> lp) {
        try {
            Field field = lp.getClass().getDeclaredField("queryType");
            field.setAccessible(true);
            Class<?> clz = (Class<?>) field.get(lp);
            return clz;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 克隆一个新的原始对象.
     * @param variable
     * @return
     */
    public <A> DynamicSQLRelationalPath<A> cloneNew(String variable) {
        DynamicSQLRelationalPath<A> relationalPath = new DynamicSQLRelationalPath(variable, this.getSchemaName(), this.getTableName());
        relationalPath.map.putAll(this.map);
        List<Path<?>> columns = this.getColumns();
        for (Path<?> c : columns) {
            ColumnMetadata cmd = this.getMetadata(c);
            relationalPath.addColumn(cmd, c.getMetadata().getName());
            Path<?> p = (Path<?>) relationalPath.copyPath(c);
            relationalPath.addMetadata(p, cmd);
        }
        PrimaryKey pk = this.getPrimaryKey();
        if (pk != null && CollectionUtils.isNotEmpty(pk.getLocalColumns())) {
            Path[] paths = (Path[]) pk.getLocalColumns().toArray(new Path[pk.getLocalColumns().size()]);
            relationalPath.createPrimaryKey(paths);
        }
        Collection<ForeignKey<?>> foreignKeys = this.getForeignKeys();
        if (CollectionUtils.isNotEmpty(foreignKeys)) {
            for (ForeignKey<?> fk : foreignKeys) {
                relationalPath.createForeignKey(fk.getLocalColumns(), fk.getForeignColumns());
            }
        }
        Collection<ForeignKey<?>> inverseForeignKeys = this.getInverseForeignKeys();
        if (CollectionUtils.isNotEmpty(inverseForeignKeys)) {
            for (ForeignKey<?> fk : inverseForeignKeys) {
                relationalPath.createInvForeignKey(fk.getLocalColumns(), fk.getForeignColumns());
            }
        }
        return relationalPath;
    }
}
